{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<h1><div align=\"center\">CUDA C/C++ Unified Memory および nvprof を使用したアクセラレーテッド アプリケーションの管理</div></h1>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![CUDA](./images/CUDA_Logo.jpg)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "CUDA 基礎のラボ補足資料 [*CUDA Best Practices Guide (CUDA ベスト プラクティス ガイド)*](http://docs.nvidia.com/cuda/cuda-c-best-practices-guide/index.html#memory-optimizations)では、Assess (評価)、Parallelize (並列化)、Optimize (最適化)、Deploy (実装) の頭文字をとった APOD という設計サイクルを推奨しています。APOD とは、アクセラレーテッド アプリケーションのパフォーマンスを段階的に改善してコードをリリースする際の、反復的な設計プロセスを規定したものです。CUDA プログラミングの知識を取得することで、アクセラレートされたコードベースにさらに高度な最適化手法を適用できるようになります。\n",
    "\n",
    "このラボでは、このような反復的開発プロセスをサポートしています。 **NVIDIA Command Line Profiler** を使用してアプリケーションのパフォーマンスを定性的に計測し、最適化できるポイントを特定します。その後、段階的に改善を適用しながら、新しい手法を学習してサイクルを繰り返していきます。このラボで実践的に学ぶ手法は、CUDA の **Unified Memory** のしくみに関連するものがほとんどです。Unified Memory の動作は、CUDA 開発者が理解しておくべき基礎的なスキルであり、他の多くの高度なメモリ管理手法を習得するための前提条件でもあります。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## 前提条件\n",
    "\n",
    "このラボを効果的に活用するには、次のスキルを習得している必要があります。\n",
    "\n",
    "- CPU 関数の呼び出しと GPU カーネルの起動の両方を行う C/C++ プログラムを作成、コンパイル、実行する。\n",
    "- 実行構成を使用して、並列スレッド階層を制御する。\n",
    "- シリアル ループをリファクタリングして、ループの反復を GPU 上で並列実行する。\n",
    "- 統合メモリの割り当てと解放を行う。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## 目標\n",
    "\n",
    "このラボを完了すると、次のことができるようになります。\n",
    "\n",
    "- **NVIDIA Command Line Profiler** (**nprof**) を使用して、アクセラレーテッド アプリケーションのパフォーマンスのプロファイルを作成する。\n",
    "- **ストリーミング マルチプロセッサ** の知識を応用して実行構成を最適化する。\n",
    "- ページ フォールトとデータ移行に関する **Unified Memory** の動作を理解する。\n",
    "- **非同期メモリ プリフェッチ** によって、ページ フォールトとデータ移行を削減してパフォーマンスを向上させる。\n",
    "- 反復的な開発サイクルを実践し、アプリケーションをすばやくアクセラレートして実装する。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## NVIDIA Command Line Profiler を使用した反復的な最適化\n",
    "\n",
    "アクセラレートされたコードベースを効果的に最適化するためには、アプリケーションのプロファイルを作成し、パフォーマンスに関する定性的な情報を把握する必要があります。 `nvprof` とは、NVIDIA Command Line Profiler のことです。CUDA ツールキットに標準搭載されており、アクセラレーテッド アプリケーションのプロファイルを作成するための強力なツールです。\n",
    "\n",
    "`nvprof`の使い方は簡単で、主に `nvcc`でコンパイルした実行可能ファイルへパスを渡すために使用します。 `nvprof` がアプリケーションを実行し、その後、そのアプリケーションの GPU アクティビティの概要、CUDA API の呼び出し、 **Unified Memory** アクティビティに関する情報などを出力します。Unified Memory については、ラボの後半で詳しく解説します。\n",
    "\n",
    "アプリケーションのアクセラレートや、既存のアクセラレーテッド アプリケーションの最適化には、科学的で反復的な手法を使用します。変更を加えた後にアプリケーションのプロファイル情報をメモして、リファクタリングがパフォーマンスに及ぼす影響を記録します。早い段階からこのような検証を頻繁に行っておくと、さまざまな場面において、最小限の労力でパフォーマンス向上を実現し、アクセラレーテッド アプリケーションをリリースできるようになります。さらに、頻繁にプロファイルを作成することで、CUDA コードベースに対する具体的な変更が実際のパフォーマンスにどのように影響しているかを把握できます。コードベースに複数の変更を加えてプロファイルを作成するだけでは、このような知識を得ることはできません。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "### 演習: nvprof を使用してアプリケーションのプロファイルを作成する\n",
    "\n",
    "[01-vector-add.cu](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF/01-vector-add/01-vector-add.cu) (<------リンクをクリックすると、ブラウザー内でソース ファイルを編集できます) は、ネイティブでアクセラレートされたベクトル加法プログラムです。以下の 2 つのコード実行セルを使用します (`CTRL` を押しながらクリックします)。 最初のコード実行セルでは、ベクトル加法プログラムをコンパイル (および実行) します。2 番目のコード実行セルでは、 `nvprof` でコンパイルされた実行可能ファイルのプロファイルを作成します。\n",
    "\n",
    "アプリケーションのプロファイルを作成したら、その出力結果に表示される情報を使用して、次の問いに答えてください。\n",
    "\n",
    "- このアプリケーションで呼び出される唯一の CUDA カーネルの名前は何か。\n",
    "- このカーネルは何回実行されたか。\n",
    "- このカーネルの実行にはどの程度時間がかかったか。アプリケーションの最適化後にどのくらい高速化できたかを比較するため、実行時間を記録しておいてください。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "!nvcc -arch=sm_70 -o single-thread-vector-add 01-vector-add/01-vector-add.cu -run"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "!nvprof ./single-thread-vector-add"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 演習: 最適化してプロファイルを作成する\n",
    "\n",
    "1 ～ 2 分程度で [01-vector-add.cu](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF/01-vector-add/01-vector-add.cu) の簡単な最適化を行います。これには、1 つのスレッド ブロック内の多数のスレッドで実行されるようにその実行構成を更新します。以下のコード実行セルを使用して、再コンパイルしてから `nvprof` でプロファイルを作成します。プロファイルの出力で、カーネルのランタイムを確認します。この最適化により、どのくらい速度が上がったでしょうか。必ず結果を記録しておいてください。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "!nvcc -arch=sm_70 -o multi-thread-vector-add 01-vector-add/01-vector-add.cu -run"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "!nvprof ./multi-thread-vector-add"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 演習: 反復的に最適化する\n",
    "\n",
    "この演習では、 [01-vector-add.cu](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF/01-vector-add/01-vector-add.cu) の実行構成の編集、プロファイル作成、結果の記録というサイクルを繰り返して、その影響を確認します。作業時には次のガイドラインに従ってください。\n",
    "\n",
    "- まず、実行構成の更新方法を 3 ～ 5 とおりリストアップします。その際は、グリッドとブロック サイズのさまざまな組み合わせを網羅してください。\n",
    "- いずれかの方法で [01-vector-add.cu](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF/01-vector-add/01-vector-add.cu) プログラムを編集します。\n",
    "- 以下の 2 つのコード実行セルを使用して、更新したコードをコンパイルしてプロファイルを作成します。\n",
    "- プロファイルの出力を見て、カーネル実行のランタイムを記録します。\n",
    "- 先ほどリストアップした最適化方法をそれぞれ試し、編集、プロファイル作成、記録のサイクルを繰り返します。\n",
    "\n",
    "試した実行条件のうち、最も高速化できたものはどれでしょうか。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "!nvcc -arch=sm_70 -o iteratively-optimized-vector-add 01-vector-add/01-vector-add.cu -run"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "!nvprof ./iteratively-optimized-vector-add"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## ストリーミング マルチプロセッサとデバイスの照会\n",
    "\n",
    "このセクションでは、最適化を促進する GPU ハードウェアの特定の機能を学びます。 **Streaming Multiprocessors** を導入したら、前述のアクセラレートされたベクトル加法プログラムをさらに最適化してみましょう。\n",
    "\n",
    "次のスライドは、このセクションの概要を視覚化した資料です。内容を確認してから、次のセクションのトピック詳細に進んでください。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "<div align=\"center\"><iframe src=\"https://docs.google.com/presentation/d/e/2PACX-1vQTzaK1iaFkxgYxaxR5QgHCVx1ZqhpX2F3q9UU6sGKCYaNIq6CGAo8W_qyzg2qwpeiZoHd7NCug7OTj/embed?start=false&loop=false&delayms=3000\" frameborder=\"0\" width=\"900\" height=\"550\" allowfullscreen=\"true\" mozallowfullscreen=\"true\" webkitallowfullscreen=\"true\"></iframe></div>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "%%HTML\n",
    "\n",
    "<div align=\"center\"><iframe src=\"https://docs.google.com/presentation/d/e/2PACX-1vQTzaK1iaFkxgYxaxR5QgHCVx1ZqhpX2F3q9UU6sGKCYaNIq6CGAo8W_qyzg2qwpeiZoHd7NCug7OTj/embed?start=false&loop=false&delayms=3000\" frameborder=\"0\" width=\"900\" height=\"550\" allowfullscreen=\"true\" mozallowfullscreen=\"true\" webkitallowfullscreen=\"true\"></iframe></div>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### ストリーミング マルチプロセッサとワープ\n",
    "\n",
    "CUDA アプリケーションが実行される GPU には、 **ストリーミング マルチプロセッサ (SM)** という処理装置があります。カーネル実行時には、スレッドのブロックが SM に渡されて実行されます。多くの場合、GPU 上の SM 数の倍数にあたるブロック数で構成されるグリッド サイズを選択することで、GPU の並行処理数が増えてパフォーマンスが向上します。\n",
    "\n",
    "さらに、SM は、 **ワープ** と呼ばれる、ブロック内の 32 個のスレッドのグループを作成、管理、スケジュール設定、実行します。[SM とワープの詳細](http://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#hardware-implementation) はこのコースの範囲外ですが、32 の倍数にあたるスレッド数で構成されるブロック サイズを選択することでパフォーマンスが向上することを覚えておくと便利です。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### GPU デバイスのプロパティのプログラムによる照会\n",
    "\n",
    "GPU 上の SM の数は使用されている GPU によって異なるため、ポータビリティをサポートするには、SM の数をコードベースにハードコーディングしなでください。代わりに、この情報をプログラムで取得するようにします。\n",
    "\n",
    "以下は、CUDA C/C++ で、現在アクティブな GPU デバイスのさまざまなプロパティ (SM 数など) を格納している C 構造体を取得するコードです。\n",
    "\n",
    "```cpp\n",
    "int deviceId;\n",
    "cudaGetDevice(&deviceId);                  // `deviceId` now points to the id of the currently active GPU.\n",
    "\n",
    "cudaDeviceProp props;\n",
    "cudaGetDeviceProperties(&props, deviceId); // `props` now has many useful properties about\n",
    "                                           // the active GPU device.\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 演習: デバイスを照会する\n",
    "\n",
    "[`01-get-device-properties.cu`](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF/04-device-properties/01-get-device-properties.cu) には未割り当ての変数が多数含まれているため、アクティブな GPU の詳細を説明する情報が出力時に文字化けします。\n",
    "\n",
    "ソース コードで示されている必要なデバイス プロパティの実際の値を出力するように、 [`01-get-device-properties.cu`](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF/04-device-properties/01-get-device-properties.cu) を構築してください。 [CUDA ランタイムに関するドキュメント](http://docs.nvidia.com/cuda/cuda-runtime-api/structcudaDeviceProp.html) は、参考情報としてデバイスの props 構造体の関連プロパティを特定するのに役立ちます。行き詰まったときは、 [解決策](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF/04-device-properties/solutions/01-get-device-properties-solution.cu) を参照してください。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "!nvcc -arch=sm_70 -o get-device-properties 04-device-properties/01-get-device-properties.cu -run"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### SM 数に合ったサイズのグリッドを使用してベクトル加法を最適化する\n",
    "\n",
    "カーネルがデバイス上の SM 数の倍数にあたるブロック数で構成されるグリッドで起動されるよう、SM 数を照会するスキルを利用して、前述の [01-vector-add.cu](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF/01-vector-add/01-vector-add.cu)内の `addVectorsInto` をリファクタリングして下さい。\n",
    "\n",
    "作成したコード内のその他の情報によって、このリファクタリングがカーネルのパフォーマンスを向上 (または変化) させるかどうかが決まります。そのため、パフォーマンスの変化を定量的に評価できるように、必ず `nvprof` を使用してください。プロファイルの出力に基づいて、これまでと同様に結果を記録します。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "!nvcc -arch=sm_70 -o sm-optimized-vector-add 01-vector-add/01-vector-add.cu -run"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "!nvprof ./sm-optimized-vector-add"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## Unified Memory の詳細\n",
    "\n",
    "ここまで、 `cudaMallocManaged` でホストまたはデバイス コード向けのメモリを割り当て、自動メモリ移行や簡単なプログラミングなどのメリットを活用してきました。しかし、 `cudaMallocManaged` で割り当てられた **Unified Memory** (**UM**) の実際の動作については詳しく取り上げていません。 `nvprof` は、アクセラレーテッド アプリケーションの UM 管理に関する詳細情報を提供します。これを UM の動作と組みあわせることで、アクセラレーテッド アプリケーションをさらに最適化できるようになります。\n",
    "\n",
    "次のスライドは、このセクションの概要を視覚化した資料です。内容を確認してから、次のセクションのトピック詳細に進んでください。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "<div align=\"center\"><iframe src=\"https://docs.google.com/presentation/d/e/2PACX-1vS0-BCGiWUb82r1RH-4cSRmZjN2vjebqoodlHIN1fvtt1iDh8X8W9WOSlLVxcsY747WVIebw13cDYBO/embed?start=false&loop=false&delayms=3000\" frameborder=\"0\" width=\"900\" height=\"550\" allowfullscreen=\"true\" mozallowfullscreen=\"true\" webkitallowfullscreen=\"true\"></iframe></div>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "%%HTML\n",
    "\n",
    "<div align=\"center\"><iframe src=\"https://docs.google.com/presentation/d/e/2PACX-1vS0-BCGiWUb82r1RH-4cSRmZjN2vjebqoodlHIN1fvtt1iDh8X8W9WOSlLVxcsY747WVIebw13cDYBO/embed?start=false&loop=false&delayms=3000\" frameborder=\"0\" width=\"900\" height=\"550\" allowfullscreen=\"true\" mozallowfullscreen=\"true\" webkitallowfullscreen=\"true\"></iframe></div>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Unified Memory のマイグレーション\n",
    "\n",
    "UM が割り当てられた時点では、ホストやデバイスにメモリはまだ存在しません。ホストまたはデバイスがメモリにアクセスしようとすると、 [ページ フォルト](https://en.wikipedia.org/wiki/Page_fault) が発生し、その時点で、ホストまたはデバイスは必要なデータを何回かに分けて移行します。同様に、CPU またはアクセラレーテッド システム内の GPU が、存在しないメモリにアクセスしようとすると、ページ フォールトが発生し、メモリの移行が開始されます。\n",
    "\n",
    "オンデマンドでページ フォールトを発生させてからメモリを移行する方法は、アクセラレーテッド アプリケーション開発がスムーズに進むため非常に便利です。また、実際に実行してみないと処理が必要かどうかわからないようなアクセス パターンの少ないデータや、アクセラレーテッド システム内の複数の GPU がアクセスするデータなどに関しては、オンデマンドのメモリ移行が非常に効果的です。\n",
    "\n",
    "実行前にデータのニーズが把握でき、連続した大規模なメモリ ブロックが必要な場合などは、ページ フォールトとのオーバーヘッド コストが生じるため、オンデマンドのデータ移行は避けた方がよいでしょう。\n",
    "\n",
    "このラボでは、オンデマンドの移行と、Profiler の出力でそれを特定する方法を理解することを目的としています。この知識を活かすことで、移行が効果的なシナリオにおいてそのオーバーヘッドを削減できるようになります。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 演習: UM のページ フォールトを確認する\n",
    "\n",
    "`nvprof` では、プロファイルを作成したアプリケーションの UM の動作に関する出力を示します。この演習では、簡単なアプリケーションにいくつかの変更を加えます。それぞれの変更後に `nvprof`の出力の [Unified Memory] セクションで UM のデータ移行の動作を確認します。\n",
    "\n",
    "[`01-page-faults.cu`](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF/06-unified-memory-page-faults/01-page-faults.cu) には `hostFunction` と `gpuKernel`が含まれています。どちらも、 `2<<24` 要素ベクトルの要素を数値 `1` で初期化する際に使用されます。この時点では、ホスト関数も GPU カーネルも使用されていません。\n",
    "\n",
    "UM の動作に関してこれまでに学習したことを踏まえ、以下の 4 つのシナリオについて、発生するページ フォールトの仮説を立ててください。その後、コードベース内の 2 つの関数のいずれかまたは両方を使用して、そのシナリオを検証できるように [`01-page-faults.cu`](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF/06-unified-memory-page-faults/01-page-faults.cu) を編集します。\n",
    "\n",
    "仮説検証用に、以下のコード実行セルを使用してコードをコンパイルし、プロファイルを作成します。4 つの実験それぞれについて、仮説と、 `nvprof` の出力結果を記録しておいてください。特に CPU および GPU のページ フォールトは重要です。4 つの実験の解答リンクが用意されているので、行き詰まったときは参照してください。\n",
    "\n",
    "- Unified memory に CPU のみがアクセスする場合 ([解答](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF/06-unified-memory-page-faults/solutions/01-page-faults-solution-cpu-only.cu))\n",
    "- Univied memoryに GPU のみがアクセスする場合 ([解答](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF/06-unified-memory-page-faults/solutions/02-page-faults-solution-gpu-only.cu))\n",
    "- 統合メモリに最初に CPU がアクセスした後、GPU がアクセスする場合 ([解答](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF/06-unified-memory-page-faults/solutions/03-page-faults-solution-cpu-then-gpu.cu))\n",
    "- 統合メモリに最初に GPU がアクセスした後、CPU がアクセスする場合 ([解答](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF/06-unified-memory-page-faults/solutions/04-page-faults-solution-gpu-then-cpu.cu))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "!nvcc -arch=sm_70 -o page-faults 06-unified-memory-page-faults/01-page-faults.cu -run"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "!nvprof ./page-faults"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 演習: ベクトル加法プログラムで UM の動作を再確認する\n",
    "\n",
    "以前使用した [01-vector-add.cu](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF/01-vector-add/01-vector-add.cu) プログラムに戻り、現在の状態のコードベースを確認してから、どのようなページ フォールトが発生するか仮説を立てます。前回のリファクタリングのプロファイル出力で (上にスクロールして出力を探すか、以下のコード実行セルを実行します)、[Unified Memory] セクションを確認します。コードベースの内容に基づいてページ フォールトの記述を説明してください。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "!nvprof ./sm-optimized-vector-add"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 演習: カーネルのベクトルを初期化する\n",
    "\n",
    "`nvprof` で、カーネル実行に要する時間が示される場合は、そのカーネルの実行中に生じるホストからデバイスへのページ フォールトとデータ移行が、表示される実行時間に含まれます。\n",
    "\n",
    "この点を考慮して、[01-vector-add.cu](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF/01-vector-add/01-vector-add.cu) プログラムの `initWith` ホスト関数を CUDA カーネルになるようにリファクタリングし、割り当てられたベクトルを GPU で並行して初期化します。リファクタリングしたアプリケーションを正常にコンパイルおよび実行できたら、プロファイルを作成する前に、以下の仮説を立てます。\n",
    "\n",
    "- リファクタリングによって UM のページ フォールトの動作にどのような影響が生じるか。\n",
    "- リファクタリングによって、レポートされた `addVectorsInto` の実行時間にどのような影響が生じるか。\n",
    "\n",
    "出力結果を記録しておいてください。行き詰まったときは、 [解決策](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF/07-init-in-kernel/solutions/01-vector-add-init-in-kernel-solution.cu) を参照してください。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "!nvcc -arch=sm_70 -o initialize-in-kernel 01-vector-add/01-vector-add.cu -run"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "!nvprof ./initialize-in-kernel"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## 非同期メモリ プリフェッチ\n",
    "\n",
    "ホストからデバイス、デバイスからホストのメモリ転送で、ページ フォールトとオンデマンドのメモリ移行に伴うオーバーヘッドを削減するには、 **非同期メモリ プリフェッチ**という手法が効果的です。この手法では、アプリケーション コードで使用する前に、バックグラウンドで統合メモリ (UM) をシステム内の CPU または GPU デバイスに非同期で移行します。これにより、ページ フォールトとオンデマンドのデータ移行のオーバーヘッドが削減されるため、GPU カーネルと CPU 機能のパフォーマンスを高めることができます。\n",
    "\n",
    "プリフェッチでは、一度に大容量のデータを移行することが多いため、オンデマンドの移行よりも移行回数が少なくなります。そのため、データ アクセスのニーズがランタイムの前にわかっている場合や、データ アクセスのパターンが多い場合に最適な手法です。\n",
    "\n",
    "CUDA では、 `cudaMemPrefetchAsync` 関数を使用することで、GPU デバイスまたは CPU へのマネージド メモリの非同期プリフェッチを簡単に行えます。この関数を使用して、現在アクティブな GPU デバイスにデータをプリフェッチしてから CPU にプリフェッチする例を次に示します。\n",
    "\n",
    "```cpp\n",
    "int deviceId;\n",
    "cudaGetDevice(&deviceId);                                         // The ID of the currently active GPU device.\n",
    "\n",
    "cudaMemPrefetchAsync(pointerToSomeUMData, size, deviceId);        // Prefetch to GPU device.\n",
    "cudaMemPrefetchAsync(pointerToSomeUMData, size, cudaCpuDeviceId); // Prefetch to host. `cudaCpuDeviceId` is a\n",
    "                                                                  // built-in CUDA variable.\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 演習: メモリをプリフェッチする\n",
    "\n",
    "ここまでで、 [01-vector-add.cu](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF/01-vector-add/01-vector-add.cu) プログラムは、CUDA カーネルを起動して 3 つ目のソリューション ベクトルに 2 つのベクトルを追加するだけでなく (すべて `cudaMallocManaged` で割り当て済み)、3 つのベクトルを並行して初期化できるようになっています。何らかの理由により、アプリケーションで上記のいずれかが実行されていない場合は、次の [リファレンス アプリケーション](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF/08-prefetch/01-vector-add-prefetch.cu)を参照し、コードベースを更新して最新の機能が反映してください。\n",
    "\n",
    "[01-vector-add.cu](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF/01-vector-add/01-vector-add.cu) アプリケーション内で `cudaMemPrefetchAsync`を使用して以下の 3 つのシナリオで実験を行い、ページ フォールトとメモリ移行への影響を把握します。 \n",
    "\n",
    "- 初期化されたベクトルの 1 つをホストにプリフェッチする場合。\n",
    "- 初期化されたベクトルの 2 つをホストにプリフェッチする場合。\n",
    "- 初期化されたベクトルの 3 つすべてをホストにプリフェッチする場合。\n",
    "\n",
    "特にページ フォールトに着目して、UM の動作とレポートされた初期化カーネルの実行時間への影響について仮説を立ててから、 `nvprof`を実行して結果を確認します。行き詰まったときは、 [解決策](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF/08-prefetch/solutions/01-vector-add-prefetch-solution.cu) を参照してください。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "!nvcc -arch=sm_70 -o prefetch-to-gpu 01-vector-add/01-vector-add.cu -run"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "!nvprof ./prefetch-to-gpu"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 演習: メモリを CPU にプリフェッチする\n",
    "\n",
    "`addVectorInto` カーネルの正確性を検証する関数を、CPU に再度プリフェッチします。今回も、 `nvprof` でプロファイルを作成する前に、UM への影響について仮説を立ててください。行き詰まったときは、 [解決策](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF/08-prefetch/solutions/02-vector-add-prefetch-solution-cpu-also.cu) を参照してください。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "!nvcc -arch=sm_70 -o prefetch-to-cpu 01-vector-add/01-vector-add.cu -run"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "!nvprof ./prefetch-to-cpu"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## まとめ\n",
    "\n",
    "ここまでで、次のことができるようになりました。\n",
    "\n",
    "- **NVIDIA Command Line Profiler** (**nvprof**) を使用して、アクセラレーテッド アプリケーションのパフォーマンスのプロファイルを作成する。\n",
    "- **ストリーミング マルチプロセッサ** の知識を応用して実行構成を最適化する。\n",
    "- ページ フォールトとデータ移行に関する **Unified Memory** の動作を理解する。\n",
    "- **非同期メモリ プリフェッチ** によって、ページ フォールトとデータ移行を削減してパフォーマンスを向上させる。\n",
    "- 反復的な開発サイクルを実践し、アプリケーションをすばやくアクセラレートして実装する。\n",
    "\n",
    "ここまでに学習した、アプリケーションを反復的にアクセラレート、最適化、実装するスキルを応用して、最後の演習に取り組んでください。この演習を完了した後に余裕のある方は、追加コンテンツに進んでください。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## 最後の演習: アクセラレートされた SAXPY アプリケーションを反復的に最適化する\n",
    "\n",
    "アクセラレートされた基本的な [SAXPY](https://en.wikipedia.org/wiki/Basic_Linear_Algebra_Subprograms#Level_1) アプリケーションが [こちら](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF/09-saxpy/01-saxpy.cu)にあります。これにはいくつかのバグが含まれています。`nvprof`で正常にコンパイルおよび実行し、プロファイルを作成するには、このバグを修正する必要があります。\n",
    "\n",
    "アプリケーションのバグを修正し、プロファイルを作成したら、 `saxpy` カーネルのランタイムを記録して、反復的にアプリケーションを最適化します。その際、反復ごとに `nvprof`を使用して、コード変更後のカーネルのパフォーマンスと UM の動作への影響を確認してください。\n",
    "\n",
    "このラボで学んだ手法を活用してください。学習効果を高めるためには、ラボ前半の説明を参照するのではなく、できる限り [自身の記憶を頼りに取り組む](http://sites.gsu.edu/scholarlyteaching/effortful-retrieval/) ようにしてください。\n",
    "\n",
    "最終目標は、 `N` を変更せずに高精度な `saxpy` カーネルのプロファイルを作成し、50us 未満で実行することです。行き詰まったときは [解決策](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF/09-saxpy/solutions/02-saxpy-solution.cu) を確認し、自由にコンパイルとプロファイル作成を行ってください。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "!nvcc -arch=sm_70 -o saxpy 09-saxpy/01-saxpy.cu -run"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "!nvprof ./saxpy"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 2",
   "language": "python",
   "name": "python2"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
